/**
 * Created by enixjin on 1/8/18.
 */
import {Subject} from "rxjs/Subject";
import {Injectable} from "@angular/core";
import {interval} from "rxjs/observable/interval";
import "rxjs/add/operator/concat";
import "rxjs/add/operator/filter";
import "rxjs/add/operator/do";
import {AbstractAppAction, AbstractRESTAction, Action, ActionType, ErrorAction} from "./action";
import {NetworkStatus} from "./action/NetworkStatus";

@Injectable()
export class Store {

    private _state: AppState;
    private _local: Map<any, any>;

    public actionSteam$: Subject<Action>;

    // public resultSteam$: Subject<Action>;

    constructor() {
        this.actionSteam$ = new Subject<Action>();
        // this.resultSteam$ = new Subject<Action>();
        // initial state
        this._state = {online: true, error: false, workingOnJob: false, restJobs: []};
        this._local = new Map<any, any>();

        // this.networkAction();
        this.appAction();
        this.restAction();
        // this.dataAction();
        // this.recoverAction();
        this.networkStatusAction();

        this.listenOnlineState();
        // this.sendRestJob();
    }

    private listenOnlineState() {
        // update online offline status every .1 second. TODO use ionic plugin
        interval(100).subscribe(
            () => {
                if (this._state.online != navigator.onLine) {
                    this.dispatch(new NetworkStatus(navigator.onLine));
                }
            }
        );
    }

    private appAction() {
        this.actionSteam$
            .filter(action => action.type === ActionType.APP)
            .subscribe(
                (action: AbstractAppAction) => {
                    console.log("update local");
                    action.updateLocal(this._local);

                    const dataAction = action.getDataAction(this._local);
                    const restAction = action.getRestAction();

                    if (this._state.online) {
                        console.log("online");
                        if (restAction) {
                            console.log(`adding rest action to job. [REST - ${action.constructor.name}], payload:${JSON.stringify(action.payload)}`);
                            this._state = Object.assign(this._state, {restJobs: [...this._state.restJobs, restAction]});
                            this.doRestJobs();
                        }
                        // it bad to know specific app action here
                        if (!action.listAction) {
                            console.log("dispatching data action for none list action");
                            this.dispatch(dataAction);
                        }
                    } else {
                        console.log(`offline`);

                        this.dispatch(dataAction);

                        // it bad to know specific app action here
                        if (!action.listAction) {
                            console.log(`adding rest action to job. [REST - ${action.constructor.name}], payload:${JSON.stringify(action.payload)}`);
                            this._state = Object.assign(this._state, {restJobs: [...this._state.restJobs, restAction]});
                        }
                    }
                }
            );
    }

    private restAction() {
        this.actionSteam$
            .filter(action => action.type === ActionType.REST)
            .subscribe(
                (action: AbstractRESTAction) => {
                    if (this._state.online) {
                        console.log(`online, execute this action and start working on job.`);
                        // mark working
                        this.getState().workingOnJob = true;
                        // send request
                        action.send()
                            .do(() => {
                                console.log(`rest action[${action.constructor.name}] complete`);
                                this._state.workingOnJob = false;
                            })
                            .subscribe(
                                result => {
                                    console.log(`rest success, update local`);
                                    action.updateLocal(this._local, result);

                                    this.doRestJobs();

                                    let dataAction = action.getDataAction(result);
                                    if (dataAction) {
                                        this.dispatch(dataAction);
                                    }
                                },
                                err => {
                                    console.log(`error while execute rest action, error:${err}`);
                                    // clear job while error.
                                    this._state = Object.assign(this._state, {restJobs: []});
                                    this._state.workingOnJob = false;
                                    this.dispatch(new ErrorAction(err.toString()));
                                });
                    } else {
                        console.log(`offline but get REST action, add this action to end of job.`);
                        this._state = Object.assign(this._state, {restJobs: [...this._state.restJobs, action]});
                    }
                }
            );
    }

    private networkStatusAction() {
        this.actionSteam$
            .filter(action => action.type === ActionType.NETWORK)
            .subscribe(
                (action: NetworkStatus) => {
                    console.log(`Network status changed to ${action.payload}.`);
                    this._state.online = action.payload;
                    // offline -> online, start work on job
                    if (this._state.online && this._state.restJobs.length > 0 && !this._state.workingOnJob) {
                        this.dispatch(this.getState().restJobs.shift());
                    }
                }
            );
    }

    private doRestJobs() {
        // TODO should be possible to optimize jobs here
        if (this._state.restJobs.length > 0 && this._state.workingOnJob === false) {
            this.dispatch(this.getState().restJobs.shift());
        }
    }

    getState(): AppState {
        return this._state;
    }

    dispatch(action: Action): void {
        console.log(`dispatching action[${ActionType[action.type]} - ${action.constructor.name}], payload:${JSON.stringify(action.payload)}`);
        this.actionSteam$.next(action);
    }

}


export interface AppState {
    online: boolean;
    error: boolean;
    restJobs: Array<AbstractRESTAction>;
    workingOnJob: boolean;
}
